/*
 * OpenTodayConfiguration.java
 *
 * Created on 16 ������ 2007, 10:31
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 *
 *Note: This source code is protected under the This software is licensed under the CC-GNU GPL.
 */

package OpenToday.Configuration;

import OpenToday.Tools.ExceptionUtils;
import java.io.*;
import java.util.TimeZone;
import javax.microedition.io.Connector;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Image;
import javax.microedition.midlet.MIDlet;
import javax.microedition.rms.RecordEnumeration;
import javax.microedition.rms.RecordStore;
import javax.microedition.rms.RecordStoreException;

/**
 *
 * @author:  Menny Even Danan (c) 2007. Note: This source code is protected under the This software is licensed under the CC-GNU GPL.
 */
public class OpenTodayConfiguration
{
    //Please note: The string can not be longer than 32 chars!
    private static final String RS_RECORD_I_MAX_EVENTS = "RS_I_MAX_EVENTS";
    private static final String RS_RECORD_I_DAYS_AHEAD = "RS_I_DAYS_AHEAD";
    private static final String RS_RECORD_I_TOP_OFFSET = "RS_I_TOP_OFFSET";
    private static final String RS_RECORD_L_REFRESH_INTERVAL = "RS_L_REFRESH_INTERVAL";
    private static final String RS_RECORD_B_SHOW_EVENTS_FROM_START_OF_DAY = "RS_B_EVENT_FROM_START_OF_DAY";
    private static final String RS_RECORD_B_SHOW_PRIVATE_EVENTS = "RS_B_SHOW_PRIVATE_EVENTS";
    private static final String RS_RECORD_S_NO_EVENTS_TEXT = "RS_S_NO_EVENTS_TEXT";
    private static final String RS_RECORD_B_TEXT_BASED_TODAY_SCREEN = "RS_B_TEXT_BASED_SCREEN";
    private static final String RS_RECORD_B_UTC_EVENTS = "RS_B_UTC_EVENTS";
    private static final String RS_RECORD_S_BACKGROUD_IMAGE= "RS_S_BACKGROUD_IMAGE";
    private static final String RS_RECORD_IA_BACKGROUD_RGB= "RS_IA_BACKGROUD_RGB";
    private static final String RS_RECORD_IA_DAY_TEXT_RGB= "RS_IA_DAY_TEXT_RGB";
    private static final String RS_RECORD_IA_EVENT_TEXT_RGB= "RS_IA_EVENT_TEXT_RGB";
    private static final String RS_RECORD_B_SHOW_OVERDUE_TASK= "RS_B_SHOW_OVERDUE";
    private static final String RS_RECORD_S_OVERDUE_TEXT= "RS_S_OVERDUE_TEXT";
    private static final String RS_RECORD_B_SHOW_DONE_TASKS= "RS_B_SHOW_DONE";
    private static final String RS_RECORD_B_SHOW_TODAY_ALWAYS= "RS_B_SHOW_TODAY_ALWAYS";
    private static final String RS_RECORD_B_DISCARD_ERRORS= "RS_B_DISCARD_ERRORS";
    private static final String RS_RECORD_I_TITLE_FONT_SIZE = "RS_I_TITLE_FONT_SIZE";
    private static final String RS_RECORD_I_EVENT_FONT_SIZE = "RS_I_EVENT_FONT_SIZE";
    private static final String RS_RECORD_B_SHOW_END_TIME = "RS_B_SHOW_END_TIME";
    
    private static OpenTodayConfiguration ms_instance;

    public synchronized static OpenTodayConfiguration getInstance()
    {
	if (ms_instance == null)
	    ms_instance = new OpenTodayConfiguration();
	
	return ms_instance;
    }
    
    private MIDlet m_currentMidlet;
    private int m_maxEvents;
    private int m_daysAhead;
    private int m_screenTopOffset;
    private long m_refreshIntervalInMS;
    private boolean m_showEventsFromStartOfToday;
    private boolean m_showPrivateEvents;
    private boolean m_TextBasedTodayScreen;
    private String m_noEventsText;
    private boolean m_UTC_events;
    private String m_backgroundImagePath;
    private ImageLoaderThread m_imageLoader;
    private int[] m_backgroundRGB;
    private int[] m_dayTextRGB;
    private int[] m_eventTextRGB;
    private boolean m_showOverdueTasks;
    private String m_overdueTasksText;
    private boolean m_showDoneTasks;
    private boolean m_showTodayAlways;
    private boolean m_discardErrors;
    private int m_eventFontSize;
    private int m_titleFontSize;
    private boolean m_showEndTime;
    
    /**
	 * Creates a new instance of OpenTodayConfiguration
	 */
    private OpenTodayConfiguration()
    {
	ExceptionUtils.PHASE("start OpenTodayConfiguration");
	m_maxEvents = 5;
	m_daysAhead = 6;
	m_screenTopOffset = 28;
	m_refreshIntervalInMS = 60 * 1000;//60 seconds
	m_showEventsFromStartOfToday = false;
	m_showPrivateEvents = true;
	m_noEventsText = "Nothing";
	m_TextBasedTodayScreen = false;
	m_UTC_events = true;
	m_backgroundImagePath = "";
	m_backgroundRGB = new int[]{255, 255, 255};
	m_dayTextRGB = new int[]{0, 0, 0};
	m_eventTextRGB = new int[]{0, 0, 0};
	m_showOverdueTasks = true;
	m_overdueTasksText = "Overdue";
	m_showDoneTasks = false;
	m_showTodayAlways = true;
	m_discardErrors = true;
        m_eventFontSize = Font.SIZE_SMALL;
        m_titleFontSize = Font.SIZE_SMALL;
        m_showEndTime  = false;
        
	ExceptionUtils.PHASE("end OpenTodayConfiguration");
	try
	{
	    //do not create if not exist. We'll create it at shutdown.
	    m_maxEvents = GetRecordInt(RS_RECORD_I_MAX_EVENTS);
	    m_daysAhead = GetRecordInt(RS_RECORD_I_DAYS_AHEAD);
	    m_screenTopOffset = GetRecordInt(RS_RECORD_I_TOP_OFFSET);
	    m_refreshIntervalInMS = GetRecordLong(RS_RECORD_L_REFRESH_INTERVAL);
	    m_showEventsFromStartOfToday = GetRecordBoolean(RS_RECORD_B_SHOW_EVENTS_FROM_START_OF_DAY);
	    m_showPrivateEvents = GetRecordBoolean(RS_RECORD_B_SHOW_PRIVATE_EVENTS);
	    m_noEventsText = GetRecordString(RS_RECORD_S_NO_EVENTS_TEXT);
	    m_TextBasedTodayScreen = GetRecordBoolean(RS_RECORD_B_TEXT_BASED_TODAY_SCREEN);
	    m_UTC_events = GetRecordBoolean(RS_RECORD_B_UTC_EVENTS);
	    m_backgroundImagePath = GetRecordString(RS_RECORD_S_BACKGROUD_IMAGE);
	    m_backgroundRGB = GetRecordIntArray(RS_RECORD_IA_BACKGROUD_RGB);
	    m_dayTextRGB = GetRecordIntArray(RS_RECORD_IA_DAY_TEXT_RGB);
	    m_eventTextRGB = GetRecordIntArray(RS_RECORD_IA_EVENT_TEXT_RGB);
	    m_showOverdueTasks = GetRecordBoolean(RS_RECORD_B_SHOW_OVERDUE_TASK);
	    m_overdueTasksText = GetRecordString(RS_RECORD_S_OVERDUE_TEXT);
	    m_showDoneTasks = GetRecordBoolean(RS_RECORD_B_SHOW_DONE_TASKS);
	    m_showTodayAlways = GetRecordBoolean(RS_RECORD_B_SHOW_TODAY_ALWAYS);
	    m_discardErrors = GetRecordBoolean(RS_RECORD_B_DISCARD_ERRORS);
            m_eventFontSize = GetRecordInt(RS_RECORD_I_EVENT_FONT_SIZE);
            m_titleFontSize = GetRecordInt(RS_RECORD_I_TITLE_FONT_SIZE);
            m_showEndTime = GetRecordBoolean(RS_RECORD_B_SHOW_END_TIME);
	}
	catch(Exception ex)
	{
	    ExceptionUtils.PHASE("RecordStore error "+ ex.getMessage());
	    System.out.println("Exception while reading configuration:" + ex.getMessage());
	    ex.printStackTrace();
	}
	m_imageLoader = new ImageLoaderThread(m_backgroundImagePath);
	ExceptionUtils.PHASE("end OpenTodayConfiguration");
    }
    
    public void InitializeOpenTodayConfiguration(MIDlet midlet)
    {
	m_currentMidlet = midlet;
    }
    
    public void Shutdown()
    {
	System.out.println("OpenTodayConfiguration.SaveConfiguration()");
	try
	{
	    //do not create if not exist. We'll create it at shutdown.
	    SaveRecord(RS_RECORD_I_MAX_EVENTS, m_maxEvents);
	    SaveRecord(RS_RECORD_I_DAYS_AHEAD, m_daysAhead);
	    SaveRecord(RS_RECORD_I_TOP_OFFSET, m_screenTopOffset);
	    SaveRecord(RS_RECORD_L_REFRESH_INTERVAL, m_refreshIntervalInMS);
	    SaveRecord(RS_RECORD_B_SHOW_EVENTS_FROM_START_OF_DAY, m_showEventsFromStartOfToday);
	    SaveRecord(RS_RECORD_B_SHOW_PRIVATE_EVENTS, m_showPrivateEvents);
	    SaveRecord(RS_RECORD_S_NO_EVENTS_TEXT, m_noEventsText);
	    SaveRecord(RS_RECORD_B_TEXT_BASED_TODAY_SCREEN, m_TextBasedTodayScreen);
	    SaveRecord(RS_RECORD_B_UTC_EVENTS, m_UTC_events);
	    SaveRecord(RS_RECORD_S_BACKGROUD_IMAGE, m_backgroundImagePath);
	    SaveRecord(RS_RECORD_IA_BACKGROUD_RGB, m_backgroundRGB);
	    SaveRecord(RS_RECORD_IA_DAY_TEXT_RGB, m_dayTextRGB);
	    SaveRecord(RS_RECORD_IA_EVENT_TEXT_RGB, m_eventTextRGB);
	    SaveRecord(RS_RECORD_B_SHOW_OVERDUE_TASK, m_showOverdueTasks);
	    SaveRecord(RS_RECORD_S_OVERDUE_TEXT, m_overdueTasksText);
	    SaveRecord(RS_RECORD_B_SHOW_DONE_TASKS, m_showDoneTasks);
	    SaveRecord(RS_RECORD_B_SHOW_TODAY_ALWAYS, m_showTodayAlways);
	    SaveRecord(RS_RECORD_B_DISCARD_ERRORS, m_discardErrors);
            SaveRecord(RS_RECORD_I_EVENT_FONT_SIZE, m_eventFontSize);
	    SaveRecord(RS_RECORD_I_TITLE_FONT_SIZE, m_titleFontSize);
            SaveRecord(RS_RECORD_B_SHOW_END_TIME, m_showEndTime);
	}
	catch(Exception ex)
	{
	    System.out.println("Exception while reading configuration:" + ex.getMessage());
	    ex.printStackTrace();
	}
	m_imageLoader.Shutdown();
    }
    
    public Display get_Display()
    {
	return Display.getDisplay(m_currentMidlet);
    }
    
    public boolean get_ShowPrivateEvents()
    {
	return m_showPrivateEvents;
    }
    
    public long get_TimeZoneOffset()
    {
	ExceptionUtils.PHASE("get_TimeZoneOffset");
	TimeZone here = TimeZone.getDefault();
	long offset = here.getRawOffset();
	if (here.useDaylightTime())
	    offset += (60 * 60 * 1000);
	ExceptionUtils.PHASE("end get_TimeZoneOffset " + offset);
	return offset;
    }
    
    public boolean get_ShowEventsFromStartOfToday()
    {
	return m_showEventsFromStartOfToday;
    }
    
    public int get_MaxEventsToShow()
    {
	return m_maxEvents;
    }
    public void set_MaxEventsToShow(int value)
    {
	m_maxEvents = value;
    }
    
    public int get_DaysAheadToLook()
    {
	return m_daysAhead;
    }
    public void set_DaysAheadToLook(int value)
    {
	m_daysAhead = value;
    }
    
    public int get_ScreenTopOffset()
    {
	return m_screenTopOffset;
    }
    public void set_ScreenTopOffset(int value)
    {
	m_screenTopOffset = value;
    }
    
    public long get_RefreshEventsListInMilliseconds()
    {
	return m_refreshIntervalInMS;
    }
    public void set_RefreshEventsListInMilliseconds(long value)
    {
	m_refreshIntervalInMS = value;
    }
    
    public String get_NoEventsText() 
    {
	    return m_noEventsText;
    }
    
    public void set_NoEventsText(String newText) 
    {
	    m_noEventsText = newText;
    }
    
    public String get_BackgroundImagePath() 
    {
	    return m_backgroundImagePath;
    }
    
    public void set_BackgroundImagePath(String fullPath) 
    {
	    m_backgroundImagePath = fullPath;
	    m_imageLoader.LoadAnImage(m_backgroundImagePath);
    }
    
    public Image GetLoadedBackgroundImage()
    {
	return m_imageLoader.GetLoadedImage();
    }
    
    public boolean get_TextBasedTodayScreen()
    {
	    return m_TextBasedTodayScreen;
    }
    
    public void set_TextBasedTodayScreen(boolean val)
    {
	    m_TextBasedTodayScreen = val;
    }
    
    public boolean get_EventsStoredInUTC()
    {
	    return m_UTC_events;
    }
    
    public void set_EventsStoredInUTC(boolean val)
    {
	    m_UTC_events = val;
    }
    
    public int[] get_BackgroundRGB() 
    {
	return m_backgroundRGB;
    }
    
    public void set_BackgroundRGB(int[] val) 
    {
	m_backgroundRGB = val;
    }
    
    public int[] get_DayTextRGB() 
    {
	return m_dayTextRGB;
    }
    
    public void set_DayTextRGB(int[] val) 
    {
	m_dayTextRGB = val;
    }
    
    public int[] get_EventTextRGB() 
    {
	return m_eventTextRGB;
    }
    
    public void set_EventTextRGB(int[] val) 
    {
	m_eventTextRGB = val;
    }
    
    public boolean getShowOverdueItems()
    {
	return m_showOverdueTasks;
    }
    
    public void setShowOverdueItems(boolean newValue)
    {
	m_showOverdueTasks = newValue;
    }
    
    public String getOverdueItemsTitle()
    {
	return m_overdueTasksText;
    }
    
    public void setOverdueItemsTitle(String newValue)
    {
	m_overdueTasksText = newValue;
    }
    
    public boolean getShowDoneTasks()
    {
	return m_showDoneTasks;
    }
    
    public void setShowDoneTasks(boolean newValue)
    {
	m_showDoneTasks = newValue;
    }
    
    public boolean getShowTodayAlways()
    {
	return m_showTodayAlways;
    }
    
    public void setShowTodayAlways(boolean newValue)
    {
	m_showTodayAlways = newValue;
    }
    
    public boolean getDiscardErrors()
    {
	return m_discardErrors;
    }
    
    public void setDiscardErrors(boolean newValue)
    {
	m_discardErrors = newValue;
    }	    
    public boolean getShowEndTime()
    {
	return m_showEndTime;
    }
    
    public void setShowEndTime(boolean newValue)
    {
	m_showEndTime = newValue;
    }	    
    public int getEventFontSize()
    {
        return m_eventFontSize;
    }
    
    public void setEventFontSize(int size)
    {
        m_eventFontSize = size;
    }
    
    public int getTitleFontSize()
    {
        return m_titleFontSize;
    }
    
    public void setTitleFontSize(int size)
    {
        m_titleFontSize = size;
    }
    
    private static byte[] CreateBytes(String text) throws IOException
    {
	ByteArrayOutputStream baos = new ByteArrayOutputStream();
	DataOutputStream outputStream = new DataOutputStream(baos);
	outputStream.writeUTF(text);
	return baos.toByteArray();	
    }

    private void SaveRecord(String rs_name, int val) throws IOException, RecordStoreException
    {
	SaveRecord(rs_name, ""+val);
    }
    
    private void SaveRecord(String rs_name, long val) throws IOException, RecordStoreException
    {
	SaveRecord(rs_name, ""+val);
    }

    private void SaveRecord(String rs_name, int[] valAI) throws IOException, RecordStoreException
    {
	SaveRecord(rs_name, GetRGBString(valAI));
    }
     
    private void SaveRecord(String rs_name, String val) throws IOException, RecordStoreException
    {
	try
	{
	   RecordStore rs = RecordStore.openRecordStore(rs_name, true, RecordStore.AUTHMODE_PRIVATE, true);
	   RecordEnumeration delE = rs.enumerateRecords(null, null, false);
	   while(delE.hasNextElement())
	       rs.deleteRecord(delE.nextRecordId());
	   delE.destroy();
	   rs.closeRecordStore();
	}
	catch(Exception ex)
	{
	    System.err.println("Exception while deleting record "+rs_name+". Error: "+ex.getMessage());
	    ex.printStackTrace();
	}
	
	try
	{
	    byte[] bytes =  val.getBytes();
	    final int STEP_SIZE = 16;
	    RecordStore rs = RecordStore.openRecordStore(rs_name, true, RecordStore.AUTHMODE_PRIVATE, true);
	    for(int i=0; i<bytes.length; i+=STEP_SIZE)
	    {
		int length = bytes.length - i;
		if (length > STEP_SIZE)
		    length = STEP_SIZE;
		rs.addRecord(bytes, i, length);
	    }
	}catch(Exception e){}
    }
    
    private void SaveRecord(String rs_name, boolean val) throws IOException, RecordStoreException
    {
	SaveRecord(rs_name, val? "Y" : "N");
    }

    private boolean GetRecordBoolean(String rs_name) throws Exception
    {
	
	String boolVal = GetRecordString(rs_name);
	return boolVal.equalsIgnoreCase("Y");
    }

    private int GetRecordInt(String rs_name)  throws Exception
    {
	String intVal = GetRecordString(rs_name);
	return Integer.parseInt(intVal);
    }
    
    public static int[] GetRGBValues(String RGB_csv) 
    {
	int[] RGB = new int[3];
	int lastComma=0;
	try
	{
	    for(int i=0;i<3;i++)
	    {
		int nextComma = RGB_csv.indexOf(",", lastComma);
		if (nextComma == -1)//no next
		    nextComma = RGB_csv.length();//till the end of the string
		String color = RGB_csv.substring(lastComma, nextComma);
		RGB[i] = Integer.parseInt(color);
		lastComma = nextComma+1;
	    }
	}
	catch(Exception ex)
	{
	    System.err.println("Error while parsing RGB! "+ex.getMessage());
	    ex.printStackTrace();
	}

	return RGB;
    }
    
    public static String GetRGBString(int[] RGB) 
    {
	return RGB[0]+","+RGB[1]+","+RGB[2];
    }
    
    private int[] GetRecordIntArray(String rs_name)  throws Exception
    {
	String intVal = GetRecordString(rs_name);
	return GetRGBValues(intVal);
    }

    private long GetRecordLong(String rs_name) throws Exception
    {
	String longVal = GetRecordString(rs_name);
	return Long.parseLong(longVal);
    }

    private String GetRecordString(String rs_name) throws Exception
    {
	RecordStore rs = RecordStore.openRecordStore(rs_name, false, RecordStore.AUTHMODE_PRIVATE, false);
	
	String val="";
	
	RecordEnumeration e = null;
	try
	{
	    e = rs.enumerateRecords(null, null, false);
	    while(e.hasNextElement())
	    {
		byte[] bytes = e.nextRecord();
		//reads in the oppisite direction?
		val = (new String(bytes)).concat(val);
	    }
	    return val;
	}
	finally
	{
	    if (e != null)
		e.destroy();
	}
    }

    private class ImageLoaderThread implements Runnable
    {
	private final Object m_monitor;
	private final Object m_loadWaiter;
	private String m_backgroundImagePath;
	private Image m_loadedBackgroundImage;
	private Thread m_thread;
	private boolean m_running;
	
	public ImageLoaderThread(String imagePath)
	{
	    m_monitor = new Object();
	    m_loadWaiter = new Object();
	    m_backgroundImagePath = imagePath;
	    m_loadedBackgroundImage = null;
	    m_running = true;
	    m_thread = new Thread(this, "ImageLoaderThread");
	    m_thread.start();
	}
	
	public void run() 
	{
	    String lastLoadedImage = "";
	    while(m_running)
	    {
		System.out.println("Loader thread b4 lock");
		synchronized(m_monitor)
		{
		    if (!lastLoadedImage.equals(m_backgroundImagePath))
		    {
			try
			{
			    String imageToLoad = "file:///" + m_backgroundImagePath;
			    System.out.println("About to load background image: "+imageToLoad);
			    InputStream in = null;
			    ByteArrayOutputStream bis = null;
			    try
			    {
				in = Connector.openInputStream(imageToLoad);
				bis = new ByteArrayOutputStream();
				int aReadByte=0;
				while((aReadByte = in.read()) != -1)
				{
				    System.out.print("r");
				    if ((bis.size() % 512) == 0)
					System.out.println("r");
				    bis.write(aReadByte);
				}   

				byte[] imageData = bis.toByteArray();
				if (imageData.length > 0)
				{
				    System.out.println("Image loaded. Size "+imageData.length);
				    in.read(imageData, 0, imageData.length);
				    System.out.println("Image data retreived. Creating image.");
				    m_loadedBackgroundImage = Image.createImage(imageData, 0, imageData.length);
				    //since we got here, it means that we loaded the image. Saving the name.
				    lastLoadedImage = m_backgroundImagePath;
				}
				else
				{
				    System.out.println("Image was not loaded. Size is zero!");
				    m_loadedBackgroundImage = null;
				    lastLoadedImage = "";//maybe next run will be better.
				}
			    }
			    finally
			    {
				if (in != null)
				{
				    try {in.close();/*waiting till something happen*/}
				    catch (Exception ex) {ex.printStackTrace();}
				}
			    }
			}
			catch(Exception ex)
			{
			    ex.printStackTrace();
			    m_loadedBackgroundImage = null;
			    lastLoadedImage = "";//maybe next run will be better.
			}
		    }
		}
		System.out.println("ImageLoader: Wait for something to happen...");
		synchronized(m_loadWaiter)
		{
		    try{m_loadWaiter.wait();}
		    catch(Exception ex){}
		}
		System.out.println("ImageLoader: Something happend!");

	    }
	    System.out.println("ImageLoader: End of thread!");
	}
	
	public void LoadAnImage(String path)
	{
	    System.out.println("LoadAnImage (b4 lock): "+path);
	    synchronized (m_monitor)
	    {
		System.out.println("LoadAnImage (after lock): "+path);
		m_backgroundImagePath = path;
	    }
	    synchronized(m_loadWaiter)
	    {
		m_loadWaiter.notifyAll();
	    }
	}
	
	public void Shutdown()
	{
	    synchronized (m_monitor)
	    {
		m_running = false;
		m_monitor.notifyAll();
	    }
	}

	private Image GetLoadedImage() 
	{
	    System.out.println("GetLoadedImage b4 lock");
	    synchronized (m_monitor)
	    {
		System.out.println("GetLoadedImage after lock");
		return m_loadedBackgroundImage;
	    }
	}
    }
}
